//
// Thread_POSIX.cpp
//
// $Id: //poco/1.4/Foundation/src/Thread_POSIX.cpp#5 $
//
// Library: Foundation
// Package: Threading
// Module:  Thread
//
// Copyright (c) 2004-2007, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/Thread_POSIX.h"
#include "Poco/Thread.h"
#include "Poco/Exception.h"
#include "Poco/ErrorHandler.h"
#include "Poco/Timespan.h"
#include "Poco/Timestamp.h"

#if POCO_OS == POCO_OS_LINUX
#	include <time.h>
#endif

#if defined(POCO_POSIX_DEBUGGER_THREAD_NAMES)


namespace
{
    void setThreadName(pthread_t thread, const char* threadName)
    {
        pthread_setname_np(thread, threadName);
    }
}


#endif


namespace Poco {


//ThreadImpl::CurrentThreadHolder ThreadImpl::m_currentThreadHolder;


ThreadImpl::ThreadImpl()
{
}


ThreadImpl::~ThreadImpl()
{
	if (m_threadData.started && !m_threadData.joined)
	{
		pthread_detach(m_threadData.thread);
	}
}


void ThreadImpl::setPriorityImpl(int prio)
{
	if (prio != m_threadData.prio)
	{
		m_threadData.prio = prio;
		m_threadData.policy = SCHED_OTHER;
		if (isRunningImpl())
		{
			struct sched_param par;
			par.sched_priority = mapPrio(m_threadData.prio, SCHED_OTHER);
			if (pthread_setschedparam(m_threadData.thread, SCHED_OTHER, &par))
				throw SystemException("cannot set thread priority");
		}
	}
}


void ThreadImpl::setOSPriorityImpl(int prio, int policy)
{
	if (prio != m_threadData.osPrio || policy != m_threadData.policy)
	{
		if (m_threadData.pRunnableTarget)
		{
			struct sched_param par;
			par.sched_priority = prio;
			if (pthread_setschedparam(m_threadData.thread, policy, &par))
				throw SystemException("cannot set thread priority");
		}
		m_threadData.prio   = reverseMapPrio(prio, policy);
		m_threadData.osPrio = prio;
		m_threadData.policy = policy;
	}
}


int ThreadImpl::getMinOSPriorityImpl(int policy)
{
#if defined(POCO_THREAD_PRIORITY_MIN)
	return POCO_THREAD_PRIORITY_MIN;
#else
	return sched_get_priority_min(policy);
#endif
}


int ThreadImpl::getMaxOSPriorityImpl(int policy)
{
#if defined(POCO_THREAD_PRIORITY_MAX)
	return POCO_THREAD_PRIORITY_MAX;
#else
	return sched_get_priority_max(policy);
#endif
}


void ThreadImpl::setStackSizeImpl(int size)
{
#ifndef PTHREAD_STACK_MIN
	m_threadData.stackSize = 0;
#else
 	m_threadData.stackSize = size;
#endif
}


void ThreadImpl::startImpl(Runnable& target)
{
	if (m_threadData.pRunnableTarget)
		throw SystemException("thread already running");

	pthread_attr_t attributes;
	pthread_attr_init(&attributes);

	if (m_threadData.stackSize != 0)
	{
		if (0 != pthread_attr_setstacksize(&attributes, m_threadData.stackSize))
		{
			pthread_attr_destroy(&attributes);
			throw SystemException("cannot set thread stack size");
		}
	}

	m_threadData.pRunnableTarget = &target;
	if (pthread_create(&m_threadData.thread, &attributes, runnableEntry, this))
	{
		m_threadData.pRunnableTarget = 0;
		pthread_attr_destroy(&attributes);
		throw SystemException("cannot start thread");
	}
	m_threadData.started = true;
	pthread_attr_destroy(&attributes);

	if (m_threadData.policy == SCHED_OTHER)
	{
		if (m_threadData.prio != PRIO_NORMAL)
		{
			struct sched_param par;
			par.sched_priority = mapPrio(m_threadData.prio, SCHED_OTHER);
			if (pthread_setschedparam(m_threadData.thread, SCHED_OTHER, &par))
				throw SystemException("cannot set thread priority");
		}
	}
	else
	{
		struct sched_param par;
		par.sched_priority = m_threadData.osPrio;
		if (pthread_setschedparam(m_threadData.thread, m_threadData.policy, &par))
			throw SystemException("cannot set thread priority");
	}
}


void ThreadImpl::joinImpl()
{
	if (!m_threadData.started) return;
	m_threadData.done.wait();
	void* result;
	if (pthread_join(m_threadData.thread, &result))
		throw SystemException("cannot join thread");
	m_threadData.joined = true;
}


bool ThreadImpl::joinImpl(long milliseconds)
{
	if (m_threadData.started && m_threadData.done.tryWait(milliseconds))
	{
		void* result;
		if (pthread_join(m_threadData.thread, &result))
			throw SystemException("cannot join thread");
		m_threadData.joined = true;
		return true;
	}
	else if (m_threadData.started) return false;
	else return true;
}


// ThreadImpl* ThreadImpl::currentImpl()
// {
// 	return m_currentThreadHolder.get();
// }


ThreadImpl::TIDImpl ThreadImpl::currentTidImpl()
{
	return pthread_self();
}


void ThreadImpl::sleepImpl(long milliseconds)
{
	Poco::Timespan remainingTime(1000*Poco::Timespan::TimeDiff(milliseconds));
	int rc;
	do
	{
		struct timespec ts;
		ts.tv_sec  = (long) remainingTime.totalSeconds();
		ts.tv_nsec = (long) remainingTime.useconds()*1000;
		Poco::Timestamp start;
		rc = ::nanosleep(&ts, 0);
		if (rc < 0 && errno == EINTR)
		{
			Poco::Timestamp end;
			Poco::Timespan waited = start.elapsed();
			if (waited < remainingTime)
				remainingTime -= waited;
			else
				remainingTime = 0;
		}
	}
	while (remainingTime > 0 && rc < 0 && errno == EINTR);
	if (rc < 0 && remainingTime > 0) throw Poco::SystemException("Thread::sleep(): nanosleep() failed");
}


void* ThreadImpl::runnableEntry(void* pThread)
{
	//m_currentThreadHolder.set(reinterpret_cast<ThreadImpl*>(pThread));

#if defined(POCO_OS_FAMILY_UNIX)
	sigset_t sset;
	sigemptyset(&sset);
	sigaddset(&sset, SIGQUIT);
	sigaddset(&sset, SIGTERM);
	sigaddset(&sset, SIGPIPE); 
	pthread_sigmask(SIG_BLOCK, &sset, 0);
#endif

	ThreadImpl* pThreadImpl = reinterpret_cast<ThreadImpl*>(pThread);
#if defined(POCO_POSIX_DEBUGGER_THREAD_NAMES)
	setThreadName(pThreadImpl->m_threadData.thread, reinterpret_cast<Thread*>(pThread)->getName().c_str());
#endif
	ThreadData* pData = &pThreadImpl->m_threadData;
	try
	{
		pData->pRunnableTarget->run();
	}
	catch (Exception& exc)
	{
		ErrorHandler::handle(exc);
	}
	catch (std::exception& exc)
	{
		ErrorHandler::handle(exc);
	}
	catch (...)
	{
		ErrorHandler::handle();
	}

	pData->pRunnableTarget = 0;
	pData->done.set();
	//data.started，还是不要改变的好，join时会用到
	return 0;
}


int ThreadImpl::mapPrio(int prio, int policy)
{
	int pmin = getMinOSPriorityImpl(policy);
	int pmax = getMaxOSPriorityImpl(policy);

	switch (prio)
	{
	case PRIO_LOWEST:
		return pmin;
	case PRIO_LOW:
		return pmin + (pmax - pmin)/4;
	case PRIO_NORMAL:
		return pmin + (pmax - pmin)/2;
	case PRIO_HIGH:
		return pmin + 3*(pmax - pmin)/4;
	case PRIO_HIGHEST:
		return pmax;
	default:
		poco_bugcheck_msg("invalid thread priority");
	}
	return -1; // just to satisfy compiler - we'll never get here anyway
}


int ThreadImpl::reverseMapPrio(int prio, int policy)
{
	if (policy == SCHED_OTHER)
	{
		int pmin = getMinOSPriorityImpl(policy);
		int pmax = getMaxOSPriorityImpl(policy);
		int normal = pmin + (pmax - pmin)/2;
		if (prio == pmax)
			return PRIO_HIGHEST;
		if (prio > normal)
			return PRIO_HIGH;
		else if (prio == normal)
			return PRIO_NORMAL;
		else if (prio > pmin)
			return PRIO_LOW;
		else
			return PRIO_LOWEST;
	}
	else return PRIO_HIGHEST;
}


} // namespace Poco
